﻿namespace ElencySolutions.MultipleProperty
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using EPiServer;
    using EPiServer.Core;
    using EPiServer.Core.Transfer;
    using EPiServer.SpecializedProperties;
    using EPiServer.Web;
    using EPiServer.Web.PropertyControls;

    /// <summary>
    /// Multiple property base class
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity, this would be a list type of object type.</typeparam>
    /// <typeparam name="TSingleEntity">The type of the single entity (non list type).</typeparam>
    [Serializable]
    public class MultiplePropertyBase<TEntity, TSingleEntity> : PropertyData, IReferenceMap, IMirroringHandlerProperty
        where TEntity : class, new()
        where TSingleEntity : class, new()
    {

        #region Members

        private TEntity _entity;

        #endregion Members

        #region Properties

        /// <summary>
        /// Gets the type of the property value.
        /// </summary>
        /// <value>
        /// The type of the property value.
        /// </value>
        public override Type PropertyValueType
        {
            get
            {
                return typeof(TEntity);
            }
        }

        /// <summary>
        /// Gets the property data type.
        /// </summary>
        public override PropertyDataType Type
        {
            get
            {
                return PropertyDataType.LongString;
            }
        }



        /// <summary>
        /// Gets or sets the value.
        /// </summary>
        /// <value>
        /// The value.
        /// </value>
        public override object Value
        {
            get
            {
                //return MultiplePropertyHelper.HandleXhtmlStringPropertyTypes(_entity);
                return _entity;
            }
            set
            {
                SetPropertyValue(value, delegate
                {
                    if (value == null)
                        Entity = MultiplePropertyHelper.CreateDefaultEntity<TEntity>();
                    else if (value is string)
                        Entity = (TEntity)Parse((string)value).Value;
                    else if (value is TEntity)
                        Entity = (TEntity)value;
                });
            }
        }

        #endregion Properties

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MultiplePropertyBase&lt;TEntity, TSingleEntity&gt;"/> class.
        /// </summary>
        public MultiplePropertyBase()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MultiplePropertyBase&lt;TEntity, TSingleEntity&gt;"/> class.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public MultiplePropertyBase(TEntity entity)
        {
            _entity = entity;
        }

        #endregion Constructors

        #region Methods

        /// <summary>
        /// Creates the property control.
        /// </summary>
        /// <returns>IPropertyControl instance</returns>
        public override IPropertyControl CreatePropertyControl()
        {
            if (!MultiplePropertyHelper.HasEntityAttribute(typeof(TEntity)))
                throw new Exception(string.Format("The MultipleProperty entity '{0}' has not been decorated with the MultiplePropertyEntityAttribute.", typeof(TEntity).FullName));

            if (!MultiplePropertyHelper.HasDataContract(typeof(TEntity)))
                throw new Exception("The entity must be decorated with either a DataContract or CollectionDataContractAttribute.");

            if (!MultiplePropertyHelper.HasKnownType(typeof(TEntity)))
                throw new Exception("The entity must be decorated with the KnownType atrribute.");

            if (typeof(TEntity).GetMethod("ToString", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public) == null)
                throw new Exception("ToString must be overriden on the MultiplePropertyEntity.  The code within the ToString method should be like the following: 'return MultiplePropertyHelper.SerializeObject(this);'.");

            return new MultiplePropertyControl<TEntity, TSingleEntity>();
        }

        /// <summary>
        /// Parses the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>Current property type</returns>
        public static MultiplePropertyBase<TEntity, TSingleEntity> Parse(string value)
        {
            return new MultiplePropertyBase<TEntity, TSingleEntity>(MultiplePropertyHelper.DeserializeObject<TEntity>(value));
        }

        /// <summary>
        /// Parses the object string value to a PropertyData object
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>PropertyData type</returns>
        public override PropertyData ParseToObject(string value)
        {
            return Parse(value);
        }

        /// <summary>
        /// Parses a string representation to the current value type
        /// </summary>
        /// <param name="value">The value.</param>
        public override void ParseToSelf(string value)
        {
            Value = Parse(value).Value;
        }

        /// <summary>
        /// Sets the default value.
        /// </summary>
        protected override void SetDefaultValue()
        {
            ThrowIfReadOnly();
            _entity = MultiplePropertyHelper.CreateDefaultEntity<TEntity>();
        }

        /// <summary>
        /// Gets or sets the entity.
        /// </summary>
        /// <value>
        /// The entity.
        /// </value>
        public virtual TEntity Entity
        {
            get
            {
                return _entity;
            }
            set
            {
                ThrowIfReadOnly();

                if ((!EqualTo(_entity, value)) || IsNull)
                {
                    _entity = value;
                    Modified();
                }
            }
        }

        /// <summary>
        /// Checks whether two objects are equal
        /// </summary>
        /// <param name="entity1">The entity1.</param>
        /// <param name="entity2">The entity2.</param>
        /// <returns></returns>
        private bool EqualTo(TEntity entity1, TEntity entity2)
        {
            if (entity1 == null || entity2 == null)
                return false;

            return string.Equals(MultiplePropertyHelper.SerializeObject(entity1), MultiplePropertyHelper.SerializeObject(entity2));
        }

        /// <summary>
        /// Loads the data.
        /// </summary>
        /// <param name="value">The value.</param>
        public override void LoadData(object value)
        {
            Value = value;
        }

        /// <summary>
        /// Saves the data.
        /// </summary>
        /// <param name="properties">The properties.</param>
        /// <returns></returns>
        public override object SaveData(PropertyDataCollection properties)
        {
            return MultiplePropertyHelper.SerializeObject(Value);
        }

        /// <summary>
        /// Gets the list item description.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        public virtual string GetListItemDescription(TSingleEntity entity)
        {
            return string.Empty;
        }

        /// <summary>
        /// Validates the entity and returns an appropriate error message if validation was unsuccessful
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <returns>An error message if validation was not successful</returns>
        public virtual string ValidateEntity(TSingleEntity entity)
        {
            return string.Empty;
        }

        /// <summary>
        /// Validates the entities.
        /// </summary>
        /// <param name="entities">The entities.</param>
        /// <returns></returns>
        public virtual string ValidateEntities(TEntity entities)
        {
            return string.Empty;
        }

        /// <summary>
        /// Creates the default view controls.
        /// </summary>
        /// <param name="control">The control.</param>
        public virtual void CreateDefaultViewControls(PropertyDataControl control)
        {
            control.Controls.Add(new LiteralControl(Value == null ? string.Empty : Value.ToString()));
        }

        #region Link re-mapping code

        /// <summary>
        /// Gets the referenced permanent link ids.
        /// </summary>
        IList<Guid> IReferenceMap.ReferencedPermanentLinkIds
        {
            get 
            { 
                List<Guid> guids = new List<Guid>();
                FindGuids(Entity, guids);
                return guids.Distinct().ToList();
            }
        }

        /// <summary>
        /// Remaps the permanent link references.
        /// </summary>
        /// <param name="idMap">The id map.</param>
        void IReferenceMap.RemapPermanentLinkReferences(IDictionary<Guid, Guid> idMap)
        {
            bool changed = false;
            Remap(idMap, Entity, ref changed);

            if (!changed) 
                return;

            ThrowIfReadOnly();
            IsModified = true;
        }

        /// <summary>
        /// Remaps the specified id map.
        /// </summary>
        /// <param name="idMap">The id map.</param>
        /// <param name="entity">The entity.</param>
        /// <param name="changed">if set to <c>true</c> [changed].</param>
        private void Remap(IDictionary<Guid, Guid> idMap, object entity, ref bool changed)
        {
            if (entity == null)
                return;

            if (entity is IList)
            {
                foreach (object currentEntity in ((IList)entity))
                    Remap(idMap, currentEntity, ref changed);

                return;
            }

            IEnumerable<PropertyInfo> properties = MultiplePropertyHelper.GetProperties(entity)
                .Where(current => MultiplePropertyHelper.GetEntityPropertyAttribute(current).Type.GetInterfaces().Count(currentInterface => currentInterface.Equals(typeof(IReferenceMap))) > 0
                    || MultiplePropertyHelper.GetEntityPropertyAttribute(current).Type.IsSubclassOf(typeof(PropertyPageReference))
                    || MultiplePropertyHelper.GetEntityPropertyAttribute(current).Type.Equals(typeof(PropertyPageReference)));

            foreach (PropertyInfo property in properties)
            {
                if (property.GetValue(entity, null) == null)
                    continue;

                MultiplePropertyEntityPropertyAttribute attribute = MultiplePropertyHelper.GetEntityPropertyAttribute(property);

                PropertyDataCollection propertyDataCollection = new PropertyDataCollection();
                propertyDataCollection.Add("Temp", Activator.CreateInstance(attribute.Type) as PropertyData);
                PropertyData propertyData = propertyDataCollection["Temp"];
                propertyData.Value = property.GetValue(entity, null);

                try
                {
                    propertyData.ParseToSelf(propertyData.Value.ToString());
                }
                catch
                {
                    propertyData.Value = property.GetValue(entity, null);
                }

                if (propertyData is IReferenceMap)
                {
                    (propertyData as IReferenceMap).RemapPermanentLinkReferences(idMap);

                    if (propertyData.IsModified)
                    {
                        changed = true;
                        property.SetValue(entity, propertyData.Value, null);

                    }
                }

                if (propertyData is PropertyPageReference && !(propertyData as PropertyPageReference).IsNull)
                {
                    PropertyPageReference propertyPageReference = (propertyData as PropertyPageReference);
                    Guid oldGuid = PermanentLinkUtility.FindGuid(propertyPageReference.PageLink);

                    if (idMap.ContainsKey(oldGuid))
                    {
                        Guid guid2 = idMap[oldGuid];

                        if (guid2 == Guid.Empty)
                            guid2 = idMap[oldGuid] = Guid.NewGuid();

                        propertyPageReference.GuidValue = guid2;
                        property.SetValue(entity, propertyPageReference.Value, null);
                        changed = true;
                    }
                }

                if (MultiplePropertyHelper.HasEntityAttribute(property.PropertyType) && property.GetValue(entity, null) != null)
                    Remap(idMap, property.GetValue(entity, null), ref changed);
            }
        }

        /// <summary>
        /// Finds the guids.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="list">The list.</param>
        private void FindGuids(object entity, List<Guid> list)
        {
            if (entity == null)
                return;

            if (entity is IList)
            {
                foreach (object currentEntity in ((IList)entity))
                    FindGuids(currentEntity, list);

                return;
            }

            IEnumerable<PropertyInfo> properties = MultiplePropertyHelper.GetProperties(entity)
                .Where(current => MultiplePropertyHelper.GetEntityPropertyAttribute(current).Type.GetInterfaces().Count(currentInterface => currentInterface.Equals(typeof(IReferenceMap))) > 0 
                    || MultiplePropertyHelper.GetEntityPropertyAttribute(current).Type.IsSubclassOf(typeof(PropertyPageReference))
                    || MultiplePropertyHelper.GetEntityPropertyAttribute(current).Type.Equals(typeof(PropertyPageReference)));

            foreach (PropertyInfo property in properties)
            {
                if (property.GetValue(entity, null) == null)
                    continue;

                MultiplePropertyEntityPropertyAttribute attribute = MultiplePropertyHelper.GetEntityPropertyAttribute(property);

                PropertyDataCollection propertyDataCollection = new PropertyDataCollection();
                propertyDataCollection.Add("Temp", Activator.CreateInstance(attribute.Type) as PropertyData);
                PropertyData propertyData = propertyDataCollection["Temp"];
                propertyData.Value = property.GetValue(entity, null);

                try
                {
                    propertyData.ParseToSelf(propertyData.Value.ToString());
                }
                catch
                {
                    propertyData.Value = property.GetValue(entity, null);
                }

                if (propertyData is IReferenceMap)
                    list.AddRange((propertyData as IReferenceMap).ReferencedPermanentLinkIds);

                if (propertyData is PropertyPageReference)
                {
                    PropertyPageReference pageReference = (propertyData as PropertyPageReference);

                    if (!PageReference.IsNullOrEmpty(pageReference.PageLink))
                        list.Add(PermanentLinkUtility.FindGuid(pageReference.PageLink));
                }

                if (MultiplePropertyHelper.HasEntityAttribute(property.PropertyType) && property.GetValue(entity, null) != null)
                    FindGuids(property.GetValue(entity, null), list);
            }
        }

        public void HandleMirroring(IPageTransferContext pageTransferContext, TransformPropertyEventArgs eventArgs, bool import)
        {
            HandleMirroring(pageTransferContext, eventArgs, Entity, import);
        }

        private void HandleMirroring(IPageTransferContext pageTransferContext, TransformPropertyEventArgs eventArgs, object entity, bool import)
        {
            if (entity == null)
                return;

            if (entity is IList)
            {
                foreach (object currentEntity in ((IList)entity))
                    HandleMirroring(pageTransferContext, eventArgs, currentEntity, import);

                return;
            }

            IEnumerable<PropertyInfo> properties = MultiplePropertyHelper.GetProperties(entity);

            foreach (PropertyInfo property in properties)
            {
                if (property.GetValue(entity, null) == null)
                    continue;

                MultiplePropertyEntityPropertyAttribute attribute = MultiplePropertyHelper.GetEntityPropertyAttribute(property);

                PropertyDataCollection propertyDataCollection = new PropertyDataCollection();
                propertyDataCollection.Add("b6c84cd7d45a4994917c463fae1e0b8b", Activator.CreateInstance(attribute.Type) as PropertyData);
                PropertyData propertyData = propertyDataCollection["b6c84cd7d45a4994917c463fae1e0b8b"];
                propertyData.Value = property.GetValue(entity, null);

                try
                {
                    propertyData.ParseToSelf(propertyData.Value.ToString());
                }
                catch
                {
                    propertyData.Value = property.GetValue(entity, null);
                }

                RawProperty rawProperty = propertyData.ToRawProperty();
                TransformPropertyEventArgs transformPropertyEventArgs = new TransformPropertyEventArgs(rawProperty, Activator.CreateInstance(attribute.Type) as PropertyData, eventArgs.ContextPage);

                if (attribute.Type.Equals(typeof(PropertyCategory)) || attribute.Type.IsSubclassOf(typeof(PropertyCategory)))
                {
                    if (import)
                        PropertyCategoryTransform.ImportEventHandler(pageTransferContext, transformPropertyEventArgs);
                    else
                        PropertyCategoryTransform.ExportEventHandler(pageTransferContext, transformPropertyEventArgs);

                    ParseAndSetEntityPropertyValue(propertyData, rawProperty, property, entity);
                }
                else if (attribute.Type.Equals(typeof(PropertyDocumentUrl)) || attribute.Type.IsSubclassOf(typeof(PropertyDocumentUrl)))
                {
                    if (import)
                        PropertyDocumentUrlTransform.ImportEventHandler(pageTransferContext, transformPropertyEventArgs);
                    else
                        PropertyDocumentUrlTransform.ExportEventHandler(pageTransferContext, transformPropertyEventArgs);

                    ParseAndSetEntityPropertyValue(propertyData, rawProperty, property, entity);
                }
                else if (attribute.Type.Equals(typeof(PropertyFrame)) || attribute.Type.IsSubclassOf(typeof(PropertyFrame)))
                {
                    if (import)
                        PropertyFrameTransform.ImportEventHandler(pageTransferContext, transformPropertyEventArgs);
                    else
                        PropertyFrameTransform.ExportEventHandler(pageTransferContext, transformPropertyEventArgs);

                    ParseAndSetEntityPropertyValue(propertyData, rawProperty, property, entity);
                }
                else if (attribute.Type.Equals(typeof(PropertyImageUrl)) || attribute.Type.IsSubclassOf(typeof(PropertyImageUrl)))
                {
                    if (import)
                        PropertyImageUrlTransform.ImportEventHandler(pageTransferContext, transformPropertyEventArgs);
                    else
                        PropertyImageUrlTransform.ExportEventHandler(pageTransferContext, transformPropertyEventArgs);

                    ParseAndSetEntityPropertyValue(propertyData, rawProperty, property, entity);
                }
                else if (attribute.Type.Equals(typeof(PropertyPageReference)) || attribute.Type.IsSubclassOf(typeof(PropertyPageReference)))
                {
                    if (import)
                        PropertyPageReferenceTransform.ImportEventHandler(pageTransferContext, transformPropertyEventArgs);
                    else
                        PropertyPageReferenceTransform.ExportEventHandler(pageTransferContext, transformPropertyEventArgs);

                    ParseAndSetEntityPropertyValue(propertyData, rawProperty, property, entity);
                }
                else if (attribute.Type.Equals(typeof(PropertyPageType)) || attribute.Type.IsSubclassOf(typeof(PropertyPageType)))
                {
                    if (import)
                        PropertyPageTypeTransform.ImportEventHandler(pageTransferContext, transformPropertyEventArgs);
                    else
                        PropertyPageTypeTransform.ExportEventHandler(pageTransferContext, transformPropertyEventArgs);

                    ParseAndSetEntityPropertyValue(propertyData, rawProperty, property, entity);
                }
                else if (attribute.Type.Equals(typeof(PropertyString)) || attribute.Type.IsSubclassOf(typeof(PropertyString)))
                {
                    if (import)
                        PropertyStringTransform.ImportEventHandler(pageTransferContext, transformPropertyEventArgs);
                    else
                        PropertyStringTransform.ExportEventHandler(pageTransferContext, transformPropertyEventArgs);

                    ParseAndSetEntityPropertyValue(propertyData, rawProperty, property, entity);
                }
                else if (attribute.Type.Equals(typeof(PropertyUrl)) || attribute.Type.IsSubclassOf(typeof(PropertyUrl)))
                {
                    if (import)
                        PropertyUrlTransform.ImportEventHandler(pageTransferContext, transformPropertyEventArgs);
                    else
                        PropertyUrlTransform.ExportEventHandler(pageTransferContext, transformPropertyEventArgs);

                    ParseAndSetEntityPropertyValue(propertyData, rawProperty, property, entity);
                }
                else if (attribute.Type.Equals(typeof(PropertyXForm)) || attribute.Type.IsSubclassOf(typeof(PropertyXForm)))
                {
                    if (import)
                        PropertyXFormTransform.ImportEventHandler(pageTransferContext, transformPropertyEventArgs);
                    else
                        PropertyXFormTransform.ExportEventHandler(pageTransferContext, transformPropertyEventArgs);

                    ParseAndSetEntityPropertyValue(propertyData, rawProperty, property, entity);
                }
                else if (attribute.Type.Equals(typeof(PropertyXhtmlString)) || attribute.Type.IsSubclassOf(typeof(PropertyXhtmlString)))
                {
                    if (import)
                        PropertyXhtmlTransform.ImportEventHandler(pageTransferContext, transformPropertyEventArgs);
                    else
                        PropertyXhtmlTransform.ExportEventHandler(pageTransferContext, transformPropertyEventArgs);

                    ParseAndSetEntityPropertyValue(propertyData, rawProperty, property, entity);
                }

                if (MultiplePropertyHelper.HasEntityAttribute(property.PropertyType) && property.GetValue(entity, null) != null)
                    HandleMirroring(pageTransferContext, eventArgs, property.GetValue(entity, null), import);
            }
        }



        private void ParseAndSetEntityPropertyValue(PropertyData propertyData, RawProperty rawProperty, PropertyInfo property, object entity)
        {
            propertyData.Value = rawProperty.Value;

            try
            {
                propertyData.ParseToSelf(rawProperty.Value);
            }
            catch
            {
                propertyData.Value = rawProperty.Value;
            }

            property.SetValue(entity, propertyData.Value, null);
        }

        #endregion Link re-mapping code

        #endregion Methods

    }
}
